### Libs
import sys, os, platform, datetime
from collections import OrderedDict



### Definitions file object
class headerFile:
	### Vars
	sections = OrderedDict()	# Sections contain a dictionary of commands/values
	numSections = 0			# Number of sections
	numCmds = 0			# Number of constants
	numLines = 0			# Number of lines in file
	
	
	### banner(sCaption): Used to section/separate output
	def banner(self, sCaption):
		print("==================================================")
		print(">>> "+sCaption)
		print("==================================================")
		return
	
	
	### display(): Debug-> Show contents of internal arrays
	def display(self):
		self.banner("Enumerate collected data")
		print("   * Total sections:  "+str(self.numSections))
		print("   * Total constants: "+str(self.numCmds)+"\n")
		
		### Display table of sections
		print("   [All sections]")
		lSections = self.sections.keys()
		for s in range(0,len(self.sections)):
			print("   " + str(s) + ".) "+lSections[s])
		
		print('\n')	# Separator
		
		### Enumerate each section's contents
		curC = 0				# Initialize constant counter for each section
		lSections = self.sections.keys()	# Get list of all section titles
		self.banner("Enumerating contents")
		for s in range(0,len(lSections)):
			curC = 0					# Reset for each section
			print("   [" + lSections[s] + "]")		# Display section title
			numC = int(len(self.sections[lSections[s]]))	# Get number of constants in section
			print("   * Number of constants: "+str(numC))	# Display  ^     ^     ^     ^     ^
			
			# Enumerate section constants
			lCmds = self.sections[lSections[s]].keys()	# Get list of constants in this section
			for c in range(0,int(len(lCmds))):
				print("     "+str(curC)+".) "+str(lCmds[c]).rstrip()+" = "+str(self.sections[lSections[s]][lCmds[c]]))	# Get stored value
				curC += 1	# Inc. section constant counter
			
			# Don't put a line after the last section... It looks odd.
			if (s < len(lSections)): print('\n')	# Separator
		return
	
	
	### formatData(sDest): Prepare a long-ass formatted string and toss it into a file.
	###                    *** Parameter specifies either output type (Arduino/Python)
	def formatData(self, sType, sFile):
		# Base setup
		sSeparator = "\\" if (platform.system == 'Windows') else "/"
		sDecl = ""	# Declaration prefix (language dependant)
		sOutput = ""	# Output buffer
		
		# Determine necessary comment prefix
		if (sType == "py"):			# Python output
			sType = "Python"
			sComment = "###"
		elif (sType == "c++"):			# Arduino/C++ output
			sType = "Arduino/C++"
			sDecl = "const unsigned int "
			sComment = "//"
		
		# Standard header
		sOutput += sComment+" VisualK-OS: Command Constants\n"
		sOutput += sComment+" Generated on: "+str("{:%B %d, %Y [%I:%Mp]}".format(datetime.datetime.now()))+"\n\n"
		
		# Iterate through data
		lSections = self.sections.keys()			# Get list of sections
		for s in range(0,len(self.sections)):
			# Section titles become comments
			sOutput += sComment+" "+lSections[s]+'\n'
			
			# Enumerate constants
			lCmds = self.sections[lSections[s]].keys()	# Get list of constants
			for c in range(0,len(self.sections[lSections[s]])):
				sName = lCmds[c].rstrip()	# Constant name
				sVal = str(self.sections[lSections[s]][lCmds[c]])	# Constant's value
				sOutput += sDecl+sName+" = "+sVal+'\n'
			sOutput += '\n'		# Separator
		
		# Check if the file exists and prompt to overwrite, if so
		sFullPath = os.getcwd()+sSeparator+sFile
		if (os.path.isfile(sFullPath)):
			sOverwrite = raw_input("[!] "+sFullPath+" already exists.\n   - Overwrite? (y/n) >> ")
			if (sOverwrite == "y"):
				print("[*] Deleting \""+sFullPath+"\"")
				os.remove(os.getcwd()+"/"+sFile)	# Delete file
			else:
				self.banner("Did not receive confirmation. Exiting.")
		
		# Dump to file
		oFile = open(sFile,'w')		# Get a handle to our file
		oFile.write(sOutput)		# Actual file writing
		oFile.close()			# We're done!
		
		# Notification
		print("[*] Success! "+sType+" output to \""+sFullPath+"\" complete.")
		return
	
	
	### loadDefinitions(): Load and parse header definition file
	def loadDefinitions(self):
		# Load definitions file
		with open('_headerDefinitions.kos', 'r') as iFile:
			### Setup
			sLastSection = ""	# Last known section header
			curC = 0		# Count of constants in current section
			self.banner("Processing input file")
			
			### Read in
			for line in iFile:	# Bypass empty lines and comments
				line.rstrip()	# Strip line-terminating characters
				
				# Check line contents... Skip blank lines, space-prefixed lines and comments
				if ((len(line) > 1) and (not line.startswith(" ")) and (not line.startswith('#'))):
					if (line.startswith('[')):
						### Encountered section header
						sTitle = line[1:(len(line)-2)]		# Extract section name
						if (sTitle == ""):			# Ensure valid section name
							### Invalid section header. Keep using the previous one.
							print("[!] Encountered empty section header at line #"+str(self.curLine)+'\n')
						else:
							### Validated, move on...
							self.numLines += 1		# Inc. working line counter
							self.numSections += 1		# Inc. section count
							print("\n  [*] New section: \""+sTitle+"\"")
							sLastSection = sTitle		# Set last known section
							
							# Create entry for new section w/cmds sub-array
							self.sections.update({sTitle:OrderedDict()})
					
					elif (line.startswith("$BASE$")):
						### Encountered value specification (sets base for sequential value assignment)
						newBase = line[7:len(line)]
						
						# Validate
						if (newBase):
							newBase = newBase.rstrip()
							print("     [*] Base value change: "+newBase)
							curC = int(newBase)	# Store
						else:
							print("     [!] Encountered invalid base value change.")
						
					else:
						### Encountered constant definition
						print("     - Found: \""+line.rstrip()+"\", value = "+str(curC))
						self.sections[sLastSection].update({line:int(curC)})	# Set constant name, assign sequential value
						curC += 1					# Inc. local section constant counter
						self.numCmds += 1				# Inc. total number of known constants
			print('\n')
		return



### Begin
os.system("clear")
print("[VisualK-OS Arduino/Python Header Generator]")

### Get input file
if(len(sys.argv) == 0):
	print("[!] No input file specified.\n* Done.")
	sys.exit()
else:
	### Input
	inF = headerFile()
	sInFile = sys.argv[0]	# Input file
	print(" - Loading definitions input file: \""+sInFile+"\"\n")
	inF.loadDefinitions()	# Load definitions file
	
	### Debug
	inF.display()		# Show contents of arrays
	
	### Output
	inF.banner("Data processed. Formatting output...")
	inF.formatData("c++","CmdConstants.h")		# Write Arduino header file
	inF.formatData("py","VKCC_Constants.py")	# Write Python include file
	sys.exit()	# Done!
